/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.properties;
import java.io.*;
import java.util.Iterator;
import javax.swing.text.BadLocationException;
import org.openide.text.PositionBounds;
/** General abstract structure for properties files, in its use similar to source hierarchy.
* Interoperates with Document, PropertiesTableModel, Nodes and other display-specific models
* Implementations of the model interfaces generally reference this structure.
*
* @author Petr Jiricka
*/
public class PropertiesStructure extends Element {
/** Holds individual items - Element.ItemElem */
private ArrayMapList items;
/** If active, contains link to its handler (parent) */
private StructHandler handler;
static final long serialVersionUID =-78380271920882131L;
/** Constructs a new PropertiesStructure for the given bounds and items. */
public PropertiesStructure(PositionBounds bounds, ArrayMapList items) {
super(bounds);
// set this structure as a parent for all elements
for (Iterator it = items.iterator(); it.hasNext(); )
((Element.ItemElem)it.next()).setParent(this);
this.items = items;
}
/** Updates the current structure by the new structure obtained by reparsing the document.
* Looks for changes between the structures and according to them calls update methods.
*/
public synchronized void update(PropertiesStructure struct) {
boolean structChanged = false;
Element.ItemElem curItem;
Element.ItemElem oldItem;
ArrayMapList new_items = struct.items;
ArrayMapList changed = new ArrayMapList();
ArrayMapList inserted = new ArrayMapList();
ArrayMapList deleted = new ArrayMapList();
for (Iterator it = new_items.iterator(); it.hasNext(); ) {
curItem = (Element.ItemElem)it.next();
curItem.setParent(this);
oldItem = getItem(curItem.getKey());
if (oldItem == null) {
inserted.add(curItem.getKey(), curItem);
}
else {
if (!curItem.equals(oldItem))
changed.add(curItem.getKey(), curItem);
items.remove(oldItem.getKey());
}
}
deleted = items;
if ((deleted.size() > 0) || (inserted.size() > 0))
structChanged = true;
// assign the new structure
items = new_items;
// notification
if (structChanged)
structureChanged(changed, inserted, deleted);
else {
// notify about changes in all items
for (Iterator it = changed.iterator(); it.hasNext(); )
itemChanged((Element.ItemElem)it.next());
}
}
/** Sets the parent of this element. */
void setParent(StructHandler parent) {
handler = parent;
}
public StructHandler getParent() {
if (handler == null)
throw new InternalError();
return handler;
}
private BundleStructure getParentBundleStructure() {
return ((PropertiesDataObject)getParent().getEntry().getDataObject()).getBundleStructure();
}
/** Get a string representation of the element for printing.
* @return the string
*/
public String printString() {
StringBuffer sb = new StringBuffer();
Element.ItemElem item;
for (Iterator it = items.iterator(); it.hasNext(); ) {
item = (Element.ItemElem)it.next();
sb.append(item.printString());
}
return sb.toString();
}
/** Get a value string of the element.
* @return the string
*/
public String toString() {
StringBuffer sb = new StringBuffer();
Element.ItemElem item;
for (Iterator it = items.iterator(); it.hasNext(); ) {
item = (Element.ItemElem)it.next();
sb.append(item.toString());
sb.append("- - -\n");
}
return sb.toString();
}
/** Adds an item to the end, should be only used by the parser (no notification)
* Used by the update actions.
*/
public void parserAddItem(Element.ItemElem item) {
item.setParent(this);
items.add(item.getKey(), item);
}
/** Retrieves an item by key (property name) or null if does not exist. */
public Element.ItemElem getItem(String key) {
return (Element.ItemElem)items.get(key);
}
/** Renames an item.
* @return true if the item has been renamed successfully, false if another item with the same name exists.
*/
public synchronized boolean renameItem(String oldKey, String newKey) {
Element.ItemElem item = getItem(newKey);
if (item == null) {
item = getItem(oldKey);
if (item == null)
return false;
item.setKey(newKey);
return true;
}
else
return false;
}
/** Deletes an item from the structure, if exists.
* @return true if the item has been deleted successfully, false if it didn't exist.
*/
public synchronized boolean deleteItem(String key) {
/*System.out.println("PropertiesStructore: deleteItem(" + key + ")");
System.out.println("------------------STRUCTURE------------------");
System.out.println(toString());
System.out.println("------------------END STRUCTURE------------------");*/
if (key == null)
key = "";
if (key.length() == 0)
return false;
Element.ItemElem item = getItem(key);
//System.out.println("-------------item --------------------\n" + item);
if (item == null)
return false;
try {
item.getBounds().setText("");
items.remove(key);
ArrayMapList deleted = new ArrayMapList();
deleted.add(key, item);
//System.out.println("-------------changing structure--------------------");
structureChanged(new ArrayMapList(), new ArrayMapList(), deleted);
/*System.out.println("------------------STRUCTURE AFTER------------------");
System.out.println(toString());
System.out.println("------------------END STRUCTURE AFTER------------------");*/
return true;
}
catch (IOException e) {
// PENDING
return false;
}
catch (BadLocationException e) {
// PENDING
return false;
}
}
/** Adds an item to the end of the file, or before the terminating comment, if exists.
* @return true if the item has been added successfully, false if another item with the same name exists.
*/
public synchronized boolean addItem(String key, String value, String comment) {
if (key == null)
key = "";
if (value == null)
value = "";
if (comment == null)
comment = "";
if (key.length() == 0 /*&& value.length() == 0 && comment.length() == 0*/)
return false;
Element.ItemElem item = getItem(key);
if (item != null)
return false;
// construct the new element
item = new Element.ItemElem(null,
new Element.KeyElem (null, key),
new Element.ValueElem (null, value),
new Element.CommentElem(null, comment));
// find the position where to add it
try {
synchronized (getParent()) {
PositionBounds pos = getSuitablePositionBoundsForInsert();
pos.insertAfter(item.printString());
getParent().reparseNowBlocking();
return true;
}
}
catch (IOException e) {
// PENDING
return false;
}
catch (BadLocationException e) {
// PENDING
return false;
}
}
/** Returns PositionBounds after which a new item may be inserted by insertAfter(String) */
private PositionBounds getSuitablePositionBoundsForInsert() {
Element.ItemElem e = null;
for (Iterator nonEmpty = nonEmptyItems(); nonEmpty.hasNext();)
e = (Element.ItemElem)nonEmpty.next();
if (e == null)
return getBounds();
else {
e.print();
return e.getBounds();
}
}
/** Returns an iterator iterating through items which have non-empty key */
public Iterator nonEmptyItems() {
return new Iterator() {
// iterator which relies on the list's iterator
private Iterator innerIt;
/** Next non-empty element in the underlying iterator */
private Element.ItemElem nextElem;
{
innerIt = items.iterator();
fetchNext();
}
/** Fetches internally the next non-empty element */
private void fetchNext() {
do {
if (innerIt.hasNext())
nextElem = (Element.ItemElem)innerIt.next();
else
nextElem = null;
}
while (nextElem != null && nextElem.getKey().length() == 0);
}
public boolean hasNext() {
return nextElem != null;
}
public Object next() {
Object ne = nextElem;
fetchNext();
return ne;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/** Returns iterator thropugh all items, including empty ones */
public Iterator allItems() {
return items.iterator();
}
/** Notification that the given item has changed (its value or comment) */
void itemChanged(Element.ItemElem elem) {
getParentBundleStructure().itemChanged(elem);
}
/** Notification that the structure has changed (no specific information). */
void structureChanged() {
getParentBundleStructure().oneFileChanged(getParent());
}
/** Notification that the structure has changed (items have been added or deleted,
* also includes changing an item's key). */
void structureChanged(ArrayMapList changed, ArrayMapList inserted, ArrayMapList deleted) {
getParentBundleStructure().oneFileChanged(getParent(), changed, inserted, deleted);
}
/** Notification that an item's key has changed. Subcase of structureChanged().
* Think twice when using this - don't I need to reparse all files ?
*/
void itemKeyChanged(String oldKey, Element.ItemElem newElem) {
// update the element in the structure, because now it is in with the wrong key
int index = items.indexOf(oldKey);
if (index < 0)
throw new InternalError();
items.set(index, newElem.getKey(), newElem);
// structural change information - watch: there may be two properties of the same name !
// maybe this is unnecessary
ArrayMapList changed = new ArrayMapList();
ArrayMapList inserted = new ArrayMapList();
ArrayMapList deleted = new ArrayMapList();
// old key
Element.ItemElem item = getItem(oldKey);
if (item == null)
// old key deleted
deleted.add(oldKey, new Element.ItemElem( null, new Element.KeyElem(null, oldKey),
new Element.ValueElem(null, "") , new Element.CommentElem(null, "")));
else
// old key changed
changed.add(item.getKey(), item);
// new key
inserted.add(newElem.getKey(), newElem);
structureChanged(changed, inserted, deleted);
}
}
/*
* <<Log>>
*/